/*************************************************************************
 *                     COPYRIGHT NOTICE
 *            Copyright 2024 Horizon Robotics, Inc.
 *                   All rights reserved.
 *************************************************************************/
#include <iostream>
#include <stdexcept>
#include <stdint.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <sys/mman.h>
#include <libdrm/drm_fourcc.h>
#include "gles_winsys.hpp"
#include "gles_shader.hpp"
#include "gles_debug.hpp"

#include "model_data.h"

class computeShaderApp {
public:
	explicit computeShaderApp(OptionParser &app_options,
				  const char* vs_shader,
				  const char* fs_shader);
	~computeShaderApp();

	void init(void);
	void run(void);
	void de_init(void);

private:
	enum {
		PATCH_NUM = 1,
		VERTICES_PER_PATCH = 4
	};
	void initWindow();
	void initGles();
	void draw();

	OptionParser &app_options;

	// Window sys info
	egl_winsys* winsys;
	// Shader info
	const char* vs_shader_name;
	const char* fs_shader_name;

	// GLES context resouurces
	GLuint texture_id;
	GLuint vao_id;
	GLuint vbo_id;
	uint32_t draw_program_id;
};

computeShaderApp::computeShaderApp(OptionParser &app_options,
				   const char* vs_shader, const char* fs_shader)
	: app_options(app_options)
	, winsys(nullptr)
	, vs_shader_name(vs_shader)
	, fs_shader_name(fs_shader)
	, vao_id(0)
	, vbo_id(0)
	{}

computeShaderApp::~computeShaderApp() {
	if (winsys != nullptr) {
		winsys->de_init();
		delete winsys;
		winsys = nullptr;
	}
}

void computeShaderApp::init() {
	initWindow();	// egl
	initGles();		// open gl
}

void computeShaderApp::run() {
	draw();
}

void computeShaderApp::de_init() {
	this->~computeShaderApp();
}

void computeShaderApp::initWindow() {
	int32_t ret = 0;

	winsys = new egl_gbm_drm_init(app_options);

	ret = winsys->init();
	if (ret != egl_winsys::INIT_SUCCESS) {
		throw std::runtime_error("winsys init failed");
	}
}

void computeShaderApp::initGles() {
	int32_t ret = 0;

	winsys->make_draw_current();

	glViewport(0,
		   0,
		   app_options.m_options[OptionParser::WIN_WIDTH].val,
		   app_options.m_options[OptionParser::WIN_HEIGHT].val);
	std::cout << "viewport w=" << app_options.m_options[OptionParser::WIN_WIDTH].val << " h=" << app_options.m_options[OptionParser::WIN_HEIGHT].val << std::endl;

	draw_program_id = create_program(vs_shader_name, fs_shader_name);
	std::cout << "main(): vs_path= " << vs_shader_name << std::endl;
	std::cout << "main(): fs_path= " << fs_shader_name << std::endl;

	glUseProgram(draw_program_id);
	
	// Triangle VAO 
	glGenVertexArrays(1, &vao_id);
	glGenBuffers(1, &vbo_id);

	glBindVertexArray(vao_id);
	glBindBuffer(GL_ARRAY_BUFFER, vbo_id);
	glBufferData(GL_ARRAY_BUFFER, sizeof(g_vertices), g_vertices, GL_STATIC_DRAW);

	glEnableVertexAttribArray(0);
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);	// position

	// framebuffer configuration
    // -------------------------
    unsigned int framebuffer;
    glGenFramebuffers(1, &framebuffer);
    glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
    // create a color attachment texture
    unsigned int textureColorbuffer;
    glGenTextures(1, &textureColorbuffer);
    glBindTexture(GL_TEXTURE_2D, textureColorbuffer);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1920, 1080, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);		// 注意内部格式参数和外部格式参数要匹配，否则framebuffer incompleled.
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureColorbuffer, 0);

	// create a renderbuffer object for depth and stencil attachment (we won't be sampling these)
    unsigned int rbo;
    glGenRenderbuffers(1, &rbo);
    glBindRenderbuffer(GL_RENDERBUFFER, rbo);
    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 1920, 1080); // use a single renderbuffer object for both a depth AND stencil buffer.
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo); // now actually attach it

    // now that we actually created the framebuffer and added all attachments we want to check if it is actually complete now

	// 检查帧缓冲完整性
    if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
        std::cerr << "Framebuffer is not complete" << std::endl;
    }

	// print info
	const GLubyte* version = glGetString(GL_VERSION);
	printf("GL_VERSION: %s\n\n", version);
}

void computeShaderApp::draw() {
	
	glEnable(GL_DEPTH_TEST);
	glClearColor(0.2f, 0.4f, 0.8f, 0.5f);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glBindVertexArray(vao_id);
	glDrawArrays(GL_TRIANGLES, 0, 3);
	glFinish();

	// 读取像素数据
    std::vector<GLubyte> pixels(1920 * 1080 * 4); // RGBA

    glReadPixels(0, 0, 1920, 1080, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());

	//winsys->swap();

	if (app_options.m_options[OptionParser::DUMP_IMAGE].val) {
		//winsys->dump_one_image();
		winsys->dump_one_image_gl(pixels);
	}
};

int main(int argc, char* argv[]) {
	OptionParser sample_options(argc, argv);
	const struct {
		std::string vertex_shader;
		std::string fragment_shader;
	} file_path_info = {
		.vertex_shader = sample_options.m_sampleBinpath + "shader.vs",
		.fragment_shader = sample_options.m_sampleBinpath + "shader.fs",
	};
	computeShaderApp app(sample_options,
			     file_path_info.vertex_shader.c_str(),
			     file_path_info.fragment_shader.c_str());
	struct timespec cur_time, end_time;
	int32_t ret = 0;

	try {
		app.init();		// egl, gles init
		app.run();		// render

	} catch (const std::exception& e) {
		std::cerr << e.what() << std::endl;
		app.de_init();
		return EXIT_FAILURE;
	}

	return EXIT_SUCCESS;
}